﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace Bamboo.Helpers
{
    public static class DateTimeExtensions
    {
        public static DateTime AddBusinessDays(this DateTime pivotDate, int businessDays, IEnumerable<DateTime> holidays)
        {
            int weekSpan = businessDays/5;

            DateTime possibleTargetDate = pivotDate.AddDays(weekSpan * 7);

            while (true)
            {
                int actualBusinessDays = CalculateBusinessDays(pivotDate, possibleTargetDate, holidays);

                if (actualBusinessDays == businessDays)
                {
                    return MondayIfInWeekend(possibleTargetDate);
                }
                else if (actualBusinessDays < businessDays) // fine tune
                {
                    possibleTargetDate = possibleTargetDate.AddDays(businessDays - actualBusinessDays);
                }
                else
                {
                    possibleTargetDate = possibleTargetDate.AddDays(-1);
                }
            }
        }

        public static int CalculateBusinessDays(DateTime startDate, DateTime endDate, IEnumerable<DateTime> holidays)
        {
            // strips off time
            startDate = startDate.Date;
            endDate = endDate.Date;

            if (startDate > endDate)
            {
                return CalculateBusinessDaysCore(endDate, startDate, holidays);
            }

            return CalculateBusinessDaysCore(startDate, endDate, holidays);
        }

        private static int CalculateBusinessDaysCore(DateTime startDate, DateTime endDate, IEnumerable<DateTime> holidays)
        {
            Contract.Requires(startDate <= endDate);

            startDate = MondayIfInWeekend(startDate);
            endDate = MondayIfInWeekend(endDate);

            var timeSpan = (endDate - startDate);

            int weekSpan = timeSpan.Days/7;

            if (startDate.DayOfWeek > endDate.DayOfWeek)
            {
                weekSpan++;
            }

            int totalDays = timeSpan.Days;

            int totalWeekendDays = weekSpan * 2;

            int totalHolidaysInvolved =
                holidays.Count(h =>
                    h >= startDate && h <= endDate 
                    && h.DayOfWeek != DayOfWeek.Saturday 
                    && h.DayOfWeek != DayOfWeek.Sunday);

            return totalDays - totalWeekendDays - totalHolidaysInvolved;
        }

        private static DateTime MondayIfInWeekend(DateTime date)
        {
            if (date.DayOfWeek == DayOfWeek.Saturday)
            {
                return date.AddDays(2);
            }

            if (date.DayOfWeek == DayOfWeek.Sunday)
            {
                return date.AddDays(1);
            }

            return date;
        }
    }
}
